package com.inetools.gwetons.client;

import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.inetools.gwetons.client.logging.JsniLogUtil;
import com.inetools.gwetons.client.logging.JsniLogUtilFactory;
import com.inetools.gwetons.client.logging.JsniLogUtilFactoryInterface;
import com.inetools.gwetons.shared.logging.LogUtil;

public class JavaScriptObjectUtil {
	// For logging --------------------------------------------------------------------------------
	static String getNameForLogging() {
		return JavaScriptObjectUtil.class.getName();
	}
	
	/* Synchronization over jsniLogUtilFactoryInterface member field is not necessary since 
	 * JavaScript is single thread environment.
	 */
	private static JsniLogUtilFactoryInterface jsniLogUtilFactoryInterface;
		protected static JsniLogUtilFactoryInterface getJsniLogUtilFactoryInterface() {
			return jsniLogUtilFactoryInterface;
		}
		protected static void setJsniLogUtilFactoryInterface(
				JsniLogUtilFactoryInterface jsniLogUtilFactoryInterface) {
			JavaScriptObjectUtil.jsniLogUtilFactoryInterface = jsniLogUtilFactoryInterface;
			setLogUtil( null);
			setJsniLogUtil( null);
		}

	/* Synchronization over logUtil member field is not necessary since 
	 * JavaScript is single thread environment.
	 */
	private static LogUtil logUtil;
		public static void setLogUtil( LogUtil logUtil) {
			JavaScriptObjectUtil.logUtil = logUtil;
			setJsniLogUtil( null);
		}
		public static LogUtil getLogUtil() {
			return JavaScriptObjectUtil.logUtil;
		}
	
	private static JsniLogUtil jsniLogUtil;
		protected static JsniLogUtil getJsniLogUtil() {
			if ( jsniLogUtil == null) {
				setJsniLogUtil( 
						JsniLogUtilFactory.getJsniLogUtil( 
								null, getLogUtil(), getNameForLogging(), getJsniLogUtilFactoryInterface())
						);
			}
			return jsniLogUtil;
		}
		protected static void setJsniLogUtil(JsniLogUtil jsniLogUtil) {
			JavaScriptObjectUtil.jsniLogUtil = jsniLogUtil;
		}
	// --------------------------------------------------------------------------------------------
	
	public static enum JavaScriptVariableType {
		BOOLEAN( "boolean"), FUNCTION( "function"), NUMBER( "number"),  
		OBJECT( "object"), STRING( "string"), UNDEFINED( "undefined");
		
		final String typeStr;
			String getTypeStr() {
				return typeStr;
			}

		JavaScriptVariableType( String typeStr) {
			this.typeStr = typeStr; 
		}
	}
	
	public static native boolean isSubstantialJavaScriptObj( JavaScriptObject jsObj) /*-{
		// Preparations for logging ---------------------------------------------------------------
		var methodName = "isSubstantialObj JSNI";
		var thisJsniLogUtil 
		= @com.inetools.gwetons.client.JavaScriptObjectUtil::getJsniLogUtil()();
		var jsniLogEntering
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getEnteringFunction()();
		var jsniLogging
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getLogFunction()();
		var jsniLogResult
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getExitingWithResultFunction()();
		// ----------------------------------------------------------------------------------------
		
		jsniLogEntering( methodName, [ "jsObj(=" + String( jsObj) + ")"]);
		
		if ( typeof( jsObj) == "undefined") {
			jsniLogging( @java.util.logging.Level::FINEST, "Type of jsObj argument is undefined.");
			jsniLogResult( methodName, false);
			return false;
		}
		if ( jsObj == null) {
			jsniLogging( @java.util.logging.Level::FINEST, "Value of jsObj argument is null.");
			jsniLogResult( methodName, false);
			return false;
		}
		
		jsniLogResult( methodName, true);
		return true;
	}-*/;
	
	/**
	 * @param jsObj
	 * @param propertyName
	 * @return true when property (specified by propertyName) of jsObj is neither undefined nor null.  
	 * @throws JavaScriptException
	 */
	public static native boolean hasSubstantialPropertyValue( 
			JavaScriptObject jsObj, String propertyName) throws JavaScriptException /*-{
		// Preparations for logging ---------------------------------------------------------------
		var methodName = "hasSubstantialPropertyValue JSNI";
		var thisJsniLogUtil 
		= @com.inetools.gwetons.client.JavaScriptObjectUtil::getJsniLogUtil()();
		var jsniLogEntering
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getEnteringFunction()();
		var jsniLogging
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getLogFunction()();
		var jsniLogExiting
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getExitingFunction()();
		var jsniLogResult
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getExitingWithResultFunction()();
		// ----------------------------------------------------------------------------------------
		
		jsniLogEntering( 
			methodName, [ "jsObj(=" + String( jsObj) + ")", "propertyName(=" + propertyName + ")"]);
		
		try {
			if ( typeof( jsObj[ propertyName]) == "undefined") {
				jsniLogging(
					@java.util.logging.Level::FINEST, 
					String( jsObj) + "[" + propertyName + "] is undefined."
					);
				jsniLogResult( methodName, false);
				return false;
			}
			if ( jsObj[ propertyName] == null) {
				jsniLogging(
					@java.util.logging.Level::FINEST, 
					"The value of " + String( jsObj) + "[" + propertyName + "] is null."
					);
				jsniLogResult( methodName, false);
				return false;
			}
			
			jsniLogResult( methodName, true);
			return true;
		}
		catch( error) {
			jsniLogging( @java.util.logging.Level::WARNING, String( error));
			jsniLogExiting( methodName);
			throw error;
		}
	}-*/;
		
	public static native boolean compareJavaScriptObjects( JavaScriptObject jsObj1, JavaScriptObject jsObj2) 
	throws JavaScriptException /*-{
		// Preparations for logging ---------------------------------------------------------------
		var methodName = "compareJavaScriptObjects JSNI";
		var thisJsniLogUtil 
		= @com.inetools.gwetons.client.JavaScriptObjectUtil::getJsniLogUtil()();
		var jsniLogEntering
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getEnteringFunction()();
		var jsniLogging
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getLogFunction()();
		var jsniLogResult
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getExitingWithResultFunction()();
		// ----------------------------------------------------------------------------------------
		
		var jsObj1Str = String( jsObj1);
		var jsObj2Str = String( jsObj2);
		
		jsniLogEntering( 
			methodName, [ "jsObj1(=" + String( jsObj1Str) + ")", "jsObj2(=" + String( jsObj2Str) + ")"]);
		
		if ( jsObj1 === jsObj2) {
			jsniLogResult( methodName, true);
			return true;
		}
		if ( (typeof( jsObj1) != "object") || (typeof( jsObj2) != "object") ) {
		// Numbers, Strings, Functions, Booleans must be strictly equal
			jsniLogging( 
				@java.util.logging.Level::FINEST, 
				"Either or both of the arguments is not object type."
				);
			jsniLogResult( methodName, false);
			return false;
		}
		if ( jsObj1.constructor !== jsObj2.constructor) {
			jsniLogging( 
				@java.util.logging.Level::FINEST, 
				"Constructors are not same."
				);
			jsniLogResult( methodName, false);
			return false;
		}
		
		for( var propertyIndex in jsObj1) {
			if ( typeof( jsObj1[ propertyIndex]) == "undefined") continue;
			
			jsniLogging(
				@java.util.logging.Level::FINEST, 
				"Comparing the values for " + propertyIndex + " property of " 
				+ "jsObj1 (=" + jsObj1Str + ") and jsObj2 (=" + jsObj2Str + ")"
				);
			
			if ( typeof( jsObj2[ propertyIndex]) == "undefined") {
				jsniLogging( 
					@java.util.logging.Level::FINEST,
					"jsObj2 argument does not have " + propertyIndex + " property."
					);
				jsniLogResult( methodName, false);
				return false;
			}
			if ( jsObj1[ propertyIndex] === jsObj2[ propertyIndex]) {
				jsniLogging(
					@java.util.logging.Level::FINEST,
					"The values for " + propertyIndex + " property of " 
					+ "jsObj1 (=" + jsObj1Str + ") and jsObj2 (=" + jsObj2Str + ") are identical."
					);
				continue; // for
			}
			if ( typeof( jsObj1[ propertyIndex]) != "object") {
			// Numbers, Strings, Functions, Booleans must be strictly equal
				jsniLogging( 
					@java.util.logging.Level::FINEST,
					"The values for " + propertyIndex + " property of " 
					+ "jsObj1 (=" + jsObj1Str + ") and jsObj2 (=" + jsObj2Str 
					+ ") are not identical."
					);
				jsniLogResult( methodName, false);
				return false;
			}
			
			// Objects and Arrays must be tested recursively
			var comparisonResult 
			= @com.inetools.gwetons.client.JavaScriptObjectUtil::compareJavaScriptObjects(Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(
					jsObj1[ propertyIndex], jsObj2[ propertyIndex]);
			if ( !comparisonResult) {
				jsniLogResult( methodName, false);
				return false;
			}
		} // for
		
		for( var propertyIndex in jsObj2) {
			if ( ( typeof( jsObj2[ propertyIndex]) != "undefined") 
				&& ( typeof( jsObj1[ propertyIndex]) == "undefined")) {
				jsniLogging( 
					@java.util.logging.Level::FINEST,
					"jsObj1 (=" + jsObj1Str + ") argument does not have " 
					+ propertyIndex + " property but jsObj2 (=" + jsObj2Str + ") does."
					);
				jsniLogResult( methodName, false);
				return false;
			}
		} // for
		
		jsniLogResult( methodName, true);
		return true;
	}-*/;
	
	static native void doAlert( SafeHtml message) /*-{
		var messageStr = message.@com.google.gwt.safehtml.shared.SafeHtml::asString()();
		alert( messageStr);
	}-*/;
	public static void alert( String message) {
		doAlert( SafeHtmlUtils.fromString( message));
	};
	
	/**
	 * Helper method to return trimmed inputStr since trim method of String class cannot be 
	 * called (like below) from inside of JSNI method:
	 * 	var nameCopy = @java.lang.String::new(Ljava/lang/String;)( name).@java.lang.String::trim()();
	 * This needs to be static method, since JavaScript class cannot be instantiated, 
	 * it seems not to allowed to call this method using "this" instance identifier like: 
	 * 	this.@com.inetools.gwetons.client.DataUnitByStyleSelector::trim(Ljava/lang/String;)( inputStr);
	 */
	public static final String trim( String inputStr) {
		return inputStr.trim();
	}

	public static native String getURIComponentDecoded( String uriComponent) 
	throws JavaScriptException /*-{
		// Just logging preparations --------------------------------------------------------------
		var methodName = "getURIComponentDecoded JSNI";
		var thisJsniLogUtil 
		= @com.inetools.gwetons.client.JavaScriptObjectUtil::getJsniLogUtil()();
		var jsniLogEntering
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getEnteringFunction()();
		var jsniLogging
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getLogFunction()();
		var jsniLogResult
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getExitingWithResultFunction()();
		// ----------------------------------------------------------------------------------------
		
		jsniLogEntering( methodName, [ "uriComponent(=" + String( uriComponent) + ")"]);
		
		try {
			var decodedResult = decodeURIComponent( uriComponent);
			jsniLogResult( methodName, decodedResult);
			return decodedResult
		}
		catch( err) {
			jsniLogging( @java.util.logging.Level::WARNING, String( err));
			jsniLogResult( methodName, null);
			return null;
		}
	}-*/;

	/*
	 * Check whether input is neither of undefined, null, or other than String.
	 */
	protected static native boolean isSubstantialStr( String questionableStr) throws JavaScriptException /*-{
		// Preparations for logging ---------------------------------------------------------------
		var methodName = "isSubstantialStr JSNI";
		var thisJsniLogUtil 
		= @com.inetools.gwetons.client.JavaScriptObjectUtil::getJsniLogUtil()();
		var jsniLogEntering
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getEnteringFunction()();
		var jsniLogging
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getLogFunction()();
		var jsniLogResult
		= thisJsniLogUtil.@com.inetools.gwetons.client.logging.JsniLogUtil::getExitingWithResultFunction()();
		// ----------------------------------------------------------------------------------------
		
		jsniLogEntering( methodName, [ "questionableStr(= " + String( questionableStr) + ")"]);
			
		if ( typeof( questionableStr) != "string") {
			if ( typeof( questionableStr) == "undefined") {
				jsniLogging(
					@java.util.logging.Level::INFO,
					"Invalid value of questionableStr argument: typeof( questionableStr) = " 
					+ typeof( questionableStr)
					);
				jsniLogResult( methodName, false);
				return false;
			}
			if ( questionableStr == null) {
				jsniLogging( 
					@java.util.logging.Level::INFO, 
					"Invalid value of questionableStr argument: null");
				jsniLogResult( methodName, false);
				return false;
			}
			jsniLogging( 
				@java.util.logging.Level::INFO,  
				"Invalid value of questionableStr argument: " + String( questionableStr));
			jsniLogResult( methodName, false);
			return false;
		}
		
		jsniLogResult( methodName, true);				
		return true;
	}-*/;
}
